package edu.hawaii.myisern.action;

import java.math.BigInteger;
import java.util.Iterator;
import java.util.List;
import edu.hawaii.myisern.collaborations.jaxb.CollaboratingOrganizations;
import edu.hawaii.myisern.collaborations.jaxb.Collaboration;
import edu.hawaii.myisern.collaborations.jaxb.CollaborationTypes;
import edu.hawaii.myisern.collaborations.jaxb.OutcomeTypes;
import edu.hawaii.myisern.collaborations.jaxb.Years;
import edu.hawaii.myisern.model.ListStringConverter;
import edu.hawaii.myisern.model.MyIsernModel;
import net.sourceforge.stripes.action.DefaultHandler;
import net.sourceforge.stripes.action.DontValidate;
import net.sourceforge.stripes.action.RedirectResolution;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.validation.SimpleError;
import net.sourceforge.stripes.validation.ValidationErrors;

/**
 * Web interface for the collaborations page.
 * 
 * @author Edward Kim
 * @author JianFei Liao
 * @author KaYee Leung
 */
public class CollaborationsActionBean implements ActionBean {
  /** The name for the collaboration. */
  private String collaborationName;
  /** The Collaboration object instance. */
  private Collaboration collaboration;
  /** An invalid collaborating organization link relates to the collaboration. */
  private String linkMessage;

  /** A String representation of the collaborating years. */
  private String yearsString;
  /** A String representation of the collaborating organizations. */
  private String organizationsString;
  /** A String representation of the outcome types. */
  private String outcomeTypesString;
  /** A String representation of the collaboration types. */
  private String typesString;
  /** A converter between String and list of String. */
  private ListStringConverter converter = new ListStringConverter();
  /** Required by the ActionBean interface. */
  private ActionBeanContext context;
  /** Our single MyIsern instance manipulated by all webapp users. */
  private MyIsernModel myModel = MyIsernModel.getInstance();

  /**
   * Returns the context. Required by the interface.
   * 
   * @return the ActionBeanContext.
   */
  public ActionBeanContext getContext() {
    return context;
  }

  /**
   * Sets the context. Required by the interface.
   * 
   * @param context The ActionBeanContext to set.
   */
  public void setContext(ActionBeanContext context) {
    this.context = context;
  }

  /**
   * Returns a collaboration.
   * 
   * @return A collaboration.
   */
  public Collaboration getCollaboration() {
    return this.collaboration;
  }

  /**
   * Sets a collaboration.
   * 
   * @param collaboration The collaboration that will be set to.
   */
  public void setCollaboration(Collaboration collaboration) {
    this.collaboration = collaboration;
  }

  /**
   * Sets the collaboration name.
   * 
   * @param name The collaboration name to set.
   */
  public void setCollaborationName(String name) {
    this.collaborationName = name;
  }

  /**
   * Sets the invalid collaborating organization link.
   * 
   * @param string The new invalid collaborating organization link.
   */
  public void setLinkMessage(String string) {
    this.linkMessage = string;
  }

  /**
   * Provides an iterator of the collaboration to the page.
   * 
   * @return an iterator that will go through all the collaborations
   */
  public Iterator<Collaboration> getCollaborationsIterator() {
    return this.myModel.getCollaborationsList().iterator();
  }

  /**
   * Sets the collaborating types.
   * 
   * @param string The new collaborating types.
   */
  public void setTypesString(String string) {
    this.typesString = string.trim();
  }

  /**
   * Gets the collaborating types.
   * 
   * @return A String representation of the collaborating types.
   */
  public String getTypesString() {
    List<String> list = this.collaboration.getCollaborationTypes().getCollaborationType();
    // convert the list to a String
    this.typesString = this.converter.listToString(list);
    return this.typesString;
  }

  /**
   * Sets the collaborating years.
   * 
   * @param string The new collaborating years.
   */
  public void setYearsString(String string) {
    this.yearsString = string.trim();
  }

  /**
   * Gets the collaborating years.
   * 
   * @return A String representation of the years.
   */
  public String getYearsString() {
    List<BigInteger> list = this.collaboration.getYears().getYear();
    // convert the list to a String
    this.yearsString = this.converter.bigIntegerListToString(list);
    return this.yearsString;
  }

  /**
   * Sets the collaborating types.
   * 
   * @param string The new collaborating types.
   */
  public void setOutcomeTypesString(String string) {
    this.outcomeTypesString = string.trim();
  }

  /**
   * Gets the collaborating types.
   * 
   * @return A String representation of the collaborating types.
   */
  public String getOutcomeTypesString() {
    List<String> list = this.collaboration.getOutcomeTypes().getOutcomeType();
    // convert the list to a String
    this.outcomeTypesString = this.converter.listToString(list);
    return this.outcomeTypesString;
  }

  /**
   * Sets the collaborating organizations.
   * 
   * @param string The new collaborating organizations.
   */
  public void setOrganizationsString(String string) {
    this.organizationsString = string.trim();
  }

  /**
   * Gets the collaborating organizations.
   * 
   * @return A String representation of the collaborating organizations.
   */
  public String getOrganizationsString() {
    List<String> list = this.collaboration.getCollaboratingOrganizations()
        .getCollaboratingOrganization();
    // convert the list to a String
    this.organizationsString = this.converter.listToString(list);
    return this.organizationsString;
  }

  /**
   * Shows information for all collaborations.
   * 
   * @return A Resolution indicating to redisplay the collaborations.jsp page.
   * @throws Exception error message
   */
  public Resolution showAllCollaborations() throws Exception {
    return new RedirectResolution("/collaborations.jsp").flash(this);
  }

  /**
   * Populates the data for the collaboration before editing.
   * 
   * @return A new resolution for editCollaboration page.
   */
  @DontValidate
  public Resolution preEditCollaboration() {
    this.collaboration = this.myModel.getSingleCollaboration(this.collaborationName.trim().replace(
        ' ', '_'));
    return new RedirectResolution("/editCollaboration.jsp").flash(this);
  }

  /**
   * Saves the changes for collaborations.
   * 
   * @throws Exception exception
   * @return A new resolution for the editCollaboration page.
   */
  @DefaultHandler
  public Resolution saveCollaboration() throws Exception {
    ValidationErrors errors = new ValidationErrors();
    // check if collaboration name is provided
    if (this.collaboration == null || this.collaboration.getName() == null) {
      errors.add("noName", new SimpleError("The collaboration name is required."));
      this.getContext().setValidationErrors(errors);
      return this.getContext().getSourcePageResolution();
    }

    // check for other data
    if (this.collaboration.getDescription() == null) {
      this.collaboration.setDescription("");
    }

    if (this.organizationsString == null) {
      errors.add("noOrganizations", new SimpleError("Collaborating organizations are required."));
      this.getContext().setValidationErrors(errors);
      return this.getContext().getSourcePageResolution();
    }
    else {
      // construct the collaborating organizations field using the organizationsStirng
      // convert the String back to a list
      List<String> organizationsList = this.converter.stringToList(this.organizationsString);
      // check if at least 2 collaborating organizations are provided
      if (organizationsList.size() < 2) {
        errors.add("lessThanTwoOrgs", 
            new SimpleError("At least two collaborating organizations are required."));
        this.getContext().setValidationErrors(errors);
        return this.getContext().getSourcePageResolution();
      }
      // create a new CollaboratingOrganizations object with the list
      CollaboratingOrganizations collaboratingOrganizations = new CollaboratingOrganizations();
      collaboratingOrganizations.getCollaboratingOrganization().clear();
      collaboratingOrganizations.getCollaboratingOrganization().addAll(organizationsList);
      // sets the collaborating organizations in the Collaboration object
      this.collaboration.setCollaboratingOrganizations(collaboratingOrganizations);
    }
    
    if (this.typesString == null) {
      errors.add("noCollabTypes", new SimpleError("The collaboration types are required."));
      this.getContext().setValidationErrors(errors);
      return this.getContext().getSourcePageResolution();
    }
    else {
      // construct the collaboration types field using the typesStirng
      // convert the String back to a list
      List<String> typesList = this.converter.stringToList(this.typesString);
      // create a new CollaborationTypes object with the list
      CollaborationTypes collaborationTypes = new CollaborationTypes();
      collaborationTypes.getCollaborationType().clear();
      collaborationTypes.getCollaborationType().addAll(typesList);
      // sets the collaboration types in the Collaboration object
      this.collaboration.setCollaborationTypes(collaborationTypes);
    }
    
    if (this.yearsString == null) {
      errors.add("noYears", new SimpleError("The collaborating years are required."));
      this.getContext().setValidationErrors(errors);
      return this.getContext().getSourcePageResolution();
    }
    else {
      try {
        // construct the years field using the yearsStirng
        // convert the String back to a list
        List<BigInteger> yearsList = this.converter.stringToBigIntegerList(this.yearsString);
        // check if the years entered are within range
        for (BigInteger year : yearsList) {
          if (!(year.intValue() == 0) && ((year.intValue() < 1990) || (year.intValue() > 2010))) {
            errors.add("yearsList", new SimpleError(
                "Collaboration years have to be between 1990 and 2010."));
            this.getContext().setValidationErrors(errors);
            return this.getContext().getSourcePageResolution();
          }
        }
        Years years = new Years();
        years.getYear().clear();
        years.getYear().addAll(yearsList);
        // sets the collaborating years in the Collaboration object
        this.collaboration.setYears(years);
      }
      catch (java.lang.NumberFormatException error) {
        errors.add("yearsList", new SimpleError("Invalid collaboration years entered."));
        this.getContext().setValidationErrors(errors);
        return this.getContext().getSourcePageResolution();
      }
    }
    
    if (this.outcomeTypesString == null) {
      errors.add("noOutcomeTypes", new SimpleError("The outcome types are required."));
      this.getContext().setValidationErrors(errors);
      return this.getContext().getSourcePageResolution();
    }
    else {
      // construct the outcome types field using the organizationsStirng
      // convert the String back to a list
      List<String> outcomeTypesList = this.converter.stringToList(this.outcomeTypesString);
      // create a new CollaboratingOrganizations object with the list
      OutcomeTypes outcomeTypes = new OutcomeTypes();
      outcomeTypes.getOutcomeType().clear();
      outcomeTypes.getOutcomeType().addAll(outcomeTypesList);
      // sets the collaborating organizations in the Collaboration object
      this.collaboration.setOutcomeTypes(outcomeTypes);
    }
    
    // store the changes and write out to the XML file
    this.myModel.addOrEditCollaboration(this.collaboration);
    this.myModel.save();
    return new RedirectResolution("/collaborations.jsp").flash(this);
  }

  /**
   * Finds the collaboration that has the invalid collaborating organization link.
   * 
   * @return A new resolution of the editCollaboration page.
   */
  public Resolution findCollaborationWithLink() {
    // parse the link message to find the collaboration
    int index = this.linkMessage.indexOf(':');
    String name = this.linkMessage.substring(index + 1).trim();
    String id = name.replace(' ', '_');
    this.collaboration = this.myModel.getCollaborationsMap().get(id);
    return new RedirectResolution("/editCollaboration.jsp").flash(this);
  }
}